﻿//
// Created by lenfrex on 2022/4/4.
//

#include <cstring>
#include <algorithm>

#include "String.h"

/**
 * 按照给定大小来初始化空的字符串数据
 */
String::String(int size) : length(0), allocSize(size) {
    data = new char[size]();
}

/**
 * 不设定初始化大小就默认128个字符来初始化
 */
String::String() : String(128) {}

/**
 * 从源中复制字符串来构建新的对象
 */
String::String(const char *source) : String(source, (int) strlen(source)) {}

/**
 * 从源中复制字符串来构建新的对象，并定义字符串实际长度，小于源字符串则会被截取
 */
String::String(const char *source, int length) : String(length + 1) {
    strncpy(data, source, length);
    data[length] = '\0';

    this->length = length;
}

String::~String() {
    delete[] data;
    data = nullptr;
}

String::String(const String &source) : String(source.data, source.length) {}

/**
 * 重载等号，其实现抽象为equals函数
 * @param source
 * @return
 */
bool String::operator==(const String &source) const {
    return equals(source.data, source.length);
}

bool String::operator==(const char *source) const {
    return equals(source, (int) strlen(source));
}

/**
 * 借鉴了Java中String类的equal()方法的实现思路。
 * 首先判断data是否为nullptr，同为null则true，否则就是false
 * 再比较两字符串长度，如果长度不一样不用说，直接返回false；
 * 相同的话再比较两字符串数据指针指向地址是否一样，一样就代表指向的是同一块内存，直接返回true
 * 两者都不同才进行逐字比较
 */
bool String::equals(const char *source, int sourceLength) const {
    // 首先判断两CString对象data成员是否为空指针
    if (source == nullptr || this->data == nullptr) {
        return source == nullptr && this->data == nullptr;
    }

    // 字符串长度不等，字符串肯定是不等
    if (length != sourceLength) {
        return false;
    } else {
        // 长度相等且data指向相同肯定等
        if (source == this->data) {
            return true;
        }
    }

    return strcmp(this->data, source) == 0;
}

String &String::operator=(const String &source) {
    if (this == &source) {
        return *this;
    }

    copyData(source.data, source.length);

    return *this;
}

String &String::operator=(const char *source) {
    if (source == nullptr) {
        delete[] data;
        this->data = nullptr;
        length = 0;
        allocSize = 0;

        return *this;
    }

    copyData(source, (int) strlen(source));

    return *this;
}

void String::copyData(const char *source, int sourceLength) {
    // 等号右侧空指针的话直接data赋为空指针
    if (source == nullptr) {
        delete[] data;
        this->data = nullptr;
        length = 0;
        allocSize = 0;

        return;
    }

    // 如果源字符串长度超过了本对象已分配的字符数组大小则重新分配长度合适的字符数组
    if (allocSize <= sourceLength) {
        allocSize = sourceLength + 1;

        delete[] data;
        data = new char[allocSize];
    }

    strcpy(data, source);
    length = sourceLength;
}

String &String::operator+=(const String &source) {
    addString(source.data, source.length);

    return *this;
}

String &String::operator+=(const char *source) {
    addString(source, (int) strlen(source));

    return *this;
}

void String::addString(const char *source, int sourceLength) {
    // nullptr不做处理
    if (source == nullptr) {
        return;
    }

    // 本对象nullptr的话就直接新分配
    if (data == nullptr) {
        allocSize = sourceLength + 1;
        this->data = new char[allocSize]();

        length = sourceLength;
    }

    // 如果拼接后字符长度超过已分配的内存大小，则重新分配data内存大小
    if (allocSize <= (length + sourceLength + 1)) {
        // 保存拼接前的原数据，以便重新分配足够的内存
        char *old = new char[length + 1]();
        strcpy(old, data);

        // 重新分配内存，防止拼接后溢出
        // 分配两倍内存空间来减少重新分配次数
        delete[] data;
        allocSize = (length + sourceLength)*2 + 1;
        this->data = new char[allocSize]();

        strcpy(data, old);
    }

    strcat(data, source);
    length += sourceLength;
}

/**
 * 按照字典顺序对比
 */
bool String::operator>(const String &compareString) const {
    // 首先判断两CString对象data成员是否为空指针，空直接返回false
    if (compareString.data == nullptr || this->data == nullptr) {
        return false;
    }

    // 两对象data成员首指向相同则对比谁长
    if (compareString.data == this->data) {
        return length > compareString.length;
    }

    // 以上情况都不符合时才进行比较
    return strcoll(this->data, compareString.data) > 0;
}

std::ostream &operator<<(std::ostream &outputStream, const String &source) {
    return outputStream << source.data;
}

std::istream &operator>>(std::istream &inputStream, String &source) {
    // 使用istream.get(char*, int)可以限制读入的长度，防止输入超长的字符串导致溢出，但是空格也会被读入
    // 手动分割太麻烦，因此只好先用这种方法了
    inputStream >> source.data;
    source.length = (int) strlen(source.data);

    return inputStream;
}

char &String::operator[](int i) {
    return (i < length && i >= 0) ? data[i] : data[0];
}

/**
 * 加号运算重载，注意，每次加号运算都会产生一个新的对象
 * 这里不用引用返回是因为返回局部对象不是安全的
 */
String String::operator+(const char *source) {
    int sourceLength = (int) strlen(source);

    String target = String((this->length + sourceLength)*2 + 1);
    target = this->data;
    target += source;

    return target;
}

String String::operator+(const String &source) {
    String target = String((this->length + source.length)*2 + 1);
    target = this->data;
    target += source;

    return target;
}

int String::Length() const {
    return length;
}
